package com.amumu.drama.common.commpont;

import com.amumu.drama.common.base.annonation.OnContextInited;
import com.amumu.drama.common.base.annonation.OnJvmShutdown;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * @author sunzhuang
 * @date 2024/9/12 15:27
 * @slogan 天下风云出我辈, 一入代码岁月催
 * @desc todo
 */
@Slf4j
@Component
public class OnContextInitializingBean implements SmartInitializingSingleton {

    private static final Logger LOG = LoggerFactory.getLogger(OnContextInitializingBean.class);

    private static final List<OnJvmShutdownMeta> onJvmShutdownList = new ArrayList<OnJvmShutdownMeta>();

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 检查方法有没有添加@{@link OnContextInited}/@{@link OnJvmShutdown}
     */
    private static void checkNoNeed(Method m) {
        if (null != m.getAnnotation(OnContextInited.class)) {
            throw new IllegalArgumentException("类" + m.getDeclaringClass().getName() + "的方法" + m.getName() + "不允许加@" + OnContextInited.class.getSimpleName());
        }
        if (null != m.getAnnotation(OnJvmShutdown.class)) {
            throw new IllegalArgumentException("类" + m.getDeclaringClass().getName() + "的方法" + m.getName() + "不允许加@" + OnJvmShutdown.class.getSimpleName());
        }
    }

    /**
     * 执行Spring初始化完成后的后续操作
     */
    private void onContextInited() throws Throwable {
        // 执行托管类中标记有@OnContextInited的无参方法，按照类全名的字典序来执行
        List<Object> beans = new ArrayList<Object>(applicationContext.getBeansOfType(Object.class, false, true).values());
        beans.sort(SpringContext.ByNameFrameworkPriorComparator.INSTANCE);
        for (Object bean : beans) {
            Class<?> clazz = bean.getClass();

            // 加快速度，对不需要处理的类加过滤
            String name = clazz.getName();
            if (!StringUtils.startsWithAny(name, "com.amumu.drama.")) {
                continue;
            }

            // 部分的类的特定方法不允许加启动注解
            if (bean instanceof BootstrapModule) {
                for (Method m : BootstrapModule.NO_NEED_FOR_OnXXX) {
                    checkNoNeed(clazz.getMethod(m.getName()));
                }
            }

            List<Method> initMethods = MethodUtils.getMethodsListWithAnnotation(clazz, OnContextInited.class, true, true);
            for (Method m : initMethods) {
                //LOG.info("onContextInitedClass:" + clazz.getName());
                long startMS = System.currentTimeMillis();
                // 为什么这里要限制protected或public方法才可以？
                // 主要是考虑对托管bean使用AOP特性时生成的类的兼容性
                // spring的AOP代理类实际是这样的，比如原始类A，代理类B
                // B是继承于A，在B的实例里面放了一个A的隐藏对象，然后把A里面所有的非private方法代理到隐藏A的对应方法去
                // private方法没有做代理，当然通过反射是能访问到，但是没有被代理到隐藏的A去，实际上调用的是B的这个方法
                // 在这个方法里面，肯定是要做一些预处理的，比如修改某个字段的值，这改的实际是B的，原始隐藏的A并没有被修改到
                // 但通过注入去实际访问的都是A，B只是一个壳而已，只有protected和public的才能代理
                if (!Modifier.isProtected(m.getModifiers()) && !Modifier.isPublic(m.getModifiers())) {
                    throw new IllegalArgumentException("类" + m.getDeclaringClass().getName() + "加@" + OnContextInited.class.getSimpleName() + "的方法" + m.getName() + "必须是protected或public的");
                }
                // 检查是不是加到有参数的方法上面去了
                if (m.getParameterTypes().length > 0) {
                    throw new IllegalArgumentException(
                            "类" + clazz.getName() + "加@" + OnContextInited.class.getSimpleName() + "的方法" + m.getName() + "不允许带有任何参数，请去掉该方法的参数或去掉@" + OnContextInited.class.getSimpleName() + "注解");
                }
                // 允许在static方法搞初始化动作，但为了防止继承带来的重复调用问题，必须要求对应的类是final的
                if (Modifier.isStatic(m.getModifiers())) {
                    if (!Modifier.isFinal(clazz.getModifiers())) {
                        throw new IllegalArgumentException(
                                "类" + clazz.getName() + "加@" + OnContextInited.class.getSimpleName() + "的方法" + m.getName() + "是static的，为了避免继承类重复调用初始化问题，请将" + clazz.getSimpleName() + "声明为final类");
                    }
                }
                m.setAccessible(true);
                try {
                    m.invoke(bean);
                } catch (Throwable e) {
                    throw new RuntimeException("类" + clazz.getName() + "加@" + OnContextInited.class.getSimpleName() + "的方法" + m.getName() + "执行过程中抛异常", e);
                } finally {
                    if (bean instanceof Advised) {
                        Advised adv = (Advised) bean;
                        name = adv.getTargetClass().getSimpleName();
                    } else {
                        name = clazz.getSimpleName();
                    }
                    LOG.info("@{} -> {}.{} [{}ms]", OnContextInited.class.getSimpleName(), name, m.getName(), (System.currentTimeMillis() - startMS));
                }
            }

            List<Method> shutdownMethods = MethodUtils.getMethodsListWithAnnotation(clazz, OnJvmShutdown.class, true, true);
            for (Method m : shutdownMethods) {
                // 检查是不是加到有参数的方法上面去了
                if (m.getParameterTypes().length > 0) {
                    throw new IllegalArgumentException(
                            "类" + clazz.getName() + "加@" + OnJvmShutdown.class.getSimpleName() + "的方法" + m.getName() + "不允许带有任何参数，请去掉该方法的参数或去掉@" + OnJvmShutdown.class.getSimpleName() + "注解");
                }
                // 允许在static方法搞关闭动作，但为了防止继承带来的重复调用问题，必须要求对应的类是final的
                if (Modifier.isStatic(m.getModifiers())) {
                    if (!Modifier.isFinal(clazz.getModifiers())) {
                        throw new IllegalArgumentException(
                                "类" + clazz.getName() + "加@" + OnJvmShutdown.class.getSimpleName() + "的方法" + m.getName() + "是static的，为了避免继承类重复调用导致重复回收等问题，请将" + clazz.getSimpleName() + "声明为final类");
                    }
                }
                m.setAccessible(true);
                onJvmShutdownList.add(new OnJvmShutdownMeta(bean, m));
            }
        }
        // 添加jvm关闭事件（System.exit时触发）
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            for (OnJvmShutdownMeta m : onJvmShutdownList) {
                try {
                    LOG.info("@{} -> {}.{}", OnJvmShutdown.class.getSimpleName(), m.bean.getClass().getSimpleName(), m.method.getName());
                    m.method.invoke(m.bean);
                } catch (Throwable e) { // 错误只记录，不阻断
                    LOG.error("类{}加@{}的方法{}执行过程中抛异常", m.bean.getClass().getName(), OnJvmShutdown.class.getSimpleName(), m.method.getName(), e);
                }
            }
        }));
    }

    @Override
    public void afterSingletonsInstantiated() {
        try {
            // 初始化Spring，内部完成@Config和@OnBeanInited初始化
            //SpringContext.contextInited = true;
            // 执行带@OnContextInited的方法
            long now = System.currentTimeMillis();
            onContextInited();
            log.warn("执行带@OnContextInited的方法耗时：{}ms", System.currentTimeMillis() - now);
        } catch (RuntimeException re) {
            throw re;
        } catch (Throwable ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 在服务器关闭时预备做的事情
     */
    private static class OnJvmShutdownMeta {

        Object bean;

        Method method;

        public OnJvmShutdownMeta(Object bean, Method method) {
            this.bean = bean;
            this.method = method;
        }
    }
}
